﻿'         This file is Copyright © 2008-2012 Billy O'Neal and Aommaster
'                            All rights reserved.
'
' This file is released under the 2-Clause BSD license, included in LICENSE.TXT
'                           and on the MHWT website
Imports System.ComponentModel
Imports System.Text.RegularExpressions
Imports System.Runtime.Serialization

<System.Serializable()> Friend Class SpeechInstance : Implements IComparable : Implements IEquatable(Of SpeechInstance)

    Public GUID As Guid
    Public Delegate Sub Generic()
    <System.NonSerialized()> Private dNonSerializableHandlers As New List(Of Generic)
    Private dSerializableHandlers As New List(Of Generic)
    Public Custom Event TerminateEditors As Generic
        AddHandler(ByVal value As Generic)
            If value.Target.GetType.IsSerializable Then
                dSerializableHandlers.Add(value)
            Else
                If dNonSerializableHandlers Is Nothing Then
                    dNonSerializableHandlers = New List(Of Generic)
                End If
                dNonSerializableHandlers.Add(value)
            End If
        End AddHandler
        RemoveHandler(ByVal value As Generic)
            If value.Target.GetType.IsSerializable Then
                dSerializableHandlers.Remove(value)
            Else
                dNonSerializableHandlers.Remove(value)
            End If
        End RemoveHandler
        RaiseEvent()
            Dim temp As New List(Of Generic)
            temp.AddRange(dNonSerializableHandlers)
            For Each item As Generic In temp
                item.Invoke()
            Next
            temp.Clear()
            temp.AddRange(dSerializableHandlers)
            For Each item As Generic In temp
                item.Invoke()
            Next
            temp.Clear()
        End RaiseEvent
    End Event
    <System.NonSerialized()> Private mNonSerializableHandlers As New List(Of Generic)
    Private mSerializableHandlers As New List(Of Generic)
    Public Custom Event Modified As Generic
        AddHandler(ByVal value As Generic)
            If value.Target.GetType.IsSerializable Then
                mSerializableHandlers.Add(value)
            Else
                If mNonSerializableHandlers Is Nothing Then
                    mNonSerializableHandlers = New List(Of Generic)
                End If
                mNonSerializableHandlers.Add(value)
            End If
        End AddHandler
        RemoveHandler(ByVal value As Generic)
            If value.Target.GetType.IsSerializable Then
                mSerializableHandlers.Remove(value)
            Else
                mNonSerializableHandlers.Remove(value)
            End If
        End RemoveHandler
        RaiseEvent()
            Dim temp As New List(Of Generic)
            temp.AddRange(mNonSerializableHandlers)
            For Each item As Generic In temp
                item.Invoke()
            Next
            temp.Clear()
            temp.AddRange(mSerializableHandlers)
            For Each item As Generic In temp
                item.Invoke()
            Next
            temp.Clear()
        End RaiseEvent
    End Event
    Private can As Speech
    <System.NonSerialized()> Private ControlsInternal As New List(Of Control)
    '
    'Define regular expressions for use in the parsing methods.
    '
    Private fieldRegex As New Regex("\[field(?:(?:\s+[""]?\s*name\s*[""]?\s*=\s*(?:""(?<name>[^""]*)""|(?<name>\b[\S]*\b)))|(?:(?:\s+[""]?\s*lines\s*[""]?\s*=\s*(?:""(?<lines>[\d]*)""|(?<lines>\b[\d]*\b))))|(?:\s+[""]?\s*mode\s*[""]?\s*=\s*(?:""(?<mode>[^""]*)""|(?<mode>\b[\S]*\b))))*\s*?\]", RegexOptions.IgnoreCase Or RegexOptions.Singleline)
    Private comboRegex As New Regex("(?>\[combobox(?:(?:\s+[""]?\s*name\s*[""]?\s*=\s*(?:""(?<name>[^""]*)""|(?<name>\b[\S]*\b)))|(?:(?:\s+[""]?\s*default\s*[""]?\s*=\s*(?:""(?<default>[^""]*)""|(?<default>\b[\S]*\b)))))*\s*\]\s*)(?<options>(?:[^[](?!\[combobox)|\[(?!combobox))*?)\s*\[/combobox\]", RegexOptions.IgnoreCase Or RegexOptions.Singleline)
    Private checkRegex As New Regex("\[checkbox(?:(?:\s+[""]?\s*name\s*[""]?\s*=\s*(?:""(?<name>[^""]*)""|(?<name>\b[\S]*\b)))|(?:(?:\s+[""]?\s*default\s*[""]?\s*=\s*(?:""(?<default>[^""]*)""|(?<default>\b[\S]*\b)))))*\s*?\]\s*(?<text>.*?)\s*\[/checkbox\]", RegexOptions.IgnoreCase Or RegexOptions.Singleline)
    Private optionRegex As New Regex("\[option(?:\s+[""]?\s*name\s*[""]?\s*=\s*(?:""(?<name>[^""]*)""|(?<name>\b[\S].*?)))\s*\]\s*(?<text>.*?)\s*\[/option\]", RegexOptions.IgnoreCase Or RegexOptions.Singleline)
    Private Sub FillControls()
        ControlsInternal = New List(Of Control)
        Dim workingText As String = can.Text
        'Dim foundcontrol As controlarray = Nothing
        'Dim temparray As New List(Of controlarray)

        For Each CheckBoxMatch As Match In checkRegex.Matches(workingText)
            If CheckBoxMatch.Groups("name").Success = False Then
                MsgBox("All checkbox tags must include a name!!")
                Exit Sub
            End If
            Dim lbl As New Label
            lbl.Margin = New Padding(3, 6, 3, 3)
            lbl.Text = CheckBoxMatch.Groups("name").Value
            lbl.Anchor = AnchorStyles.Top Or AnchorStyles.Right
            lbl.Width = getTextWidth(lbl.Text)
            lbl.AutoSize = True
            Dim cb As New CheckBox
            cb.Text = ""
            cb.Name = CheckBoxMatch.Groups("name").Value
            If CheckBoxMatch.Groups("default").Success Then
                If CheckBoxMatch.Groups("default").Value.ToLower() = "no" Or CheckBoxMatch.Groups("default").Value.ToLower() = "off" Or CheckBoxMatch.Groups("default").Value.ToLower() = "false" Then
                    cb.Checked = False
                Else
                    cb.Checked = True
                End If
            Else
                cb.Checked = True
            End If
            'foundcontrol = New controlarray
            'foundcontrol.index = CheckBoxMatch.Index
            'foundcontrol.control = cb
            'foundcontrol.label = lbl
            'temparray.Add(foundcontrol)

            ControlsInternal.Add(lbl)
            ControlsInternal.Add(cb)
        Next
        For Each comboMatch As Match In comboRegex.Matches(workingText)
            If comboMatch.Groups("name").Success = False Then
                MsgBox("All combobox tags must include a name!!")
                Exit Sub
            End If
            Dim lbl As New Label
            lbl.Margin = New Padding(3, 6, 3, 3)
            lbl.Text = comboMatch.Groups("name").Value
            lbl.Anchor = AnchorStyles.Top Or AnchorStyles.Right
            lbl.Width = getTextWidth(lbl.Text)
            lbl.AutoSize = True
            Dim cb As New ComboBox
            cb.Name = comboMatch.Groups("name").Value
            cb.Anchor = AnchorStyles.Top Or AnchorStyles.Left Or AnchorStyles.Right
            If comboMatch.Groups("default").Success Then
                cb.Text = comboMatch.Groups("default").Value
            Else
                cb.Text = ""
            End If
            For Each optionMatch As Match In optionRegex.Matches(comboMatch.Groups("options").Value)
                If optionMatch.Groups("name").Success Then
                    cb.Items.Add(optionMatch.Groups("name").Value)
                Else
                    MsgBox("All options must have names!!")
                End If
            Next
            cb.Width = 0
            'foundcontrol = New controlarray
            'foundcontrol.index = comboMatch.Index
            'foundcontrol.control = cb
            'foundcontrol.label = lbl
            'temparray.Add(foundcontrol)
            ControlsInternal.Add(lbl)
            ControlsInternal.Add(cb)
        Next
        For Each fieldMatch As Match In fieldRegex.Matches(workingText)
            If fieldMatch.Groups("name").Success = False Then
                MsgBox("All field tags must include a name!!")
                Exit Sub
            End If
            Dim lbl As New Label
            lbl.Margin = New Padding(3, 6, 3, 3)
            lbl.Text = fieldMatch.Groups("name").Value
            lbl.Anchor = AnchorStyles.Top Or AnchorStyles.Right
            lbl.Width = getTextWidth(lbl.Text)
            lbl.AutoSize = True
            Dim tb As New TextBox
            tb.Name = fieldMatch.Groups("name").Value
            tb.Width = 0
            tb.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
            If fieldMatch.Groups("lines").Success Then
                tb.Multiline = True
                tb.WordWrap = False
                tb.ScrollBars = ScrollBars.Both
                tb.Anchor = AnchorStyles.Left Or AnchorStyles.Top Or AnchorStyles.Right
                tb.Height = getTextHeight(Convert.ToInt32(fieldMatch.Groups("lines").Value()))
            End If

            'foundcontrol = New controlarray
            'foundcontrol.index = fieldMatch.Index
            'foundcontrol.control = tb
            'foundcontrol.label = lbl
            'temparray.Add(foundcontrol)
            ControlsInternal.Add(lbl)
            ControlsInternal.Add(tb)
        Next
        'temparray.Sort(foundcontrol.index)


        'For Each Control As controlarray In temparray

        'Next
    End Sub
    Public Sub New(ByVal Speech As Speech)
        can = Speech
        FillControls()
    End Sub
    Public Sub New()

    End Sub
    Public Overrides Function ToString() As String
        Return can.ToString()
    End Function
    Function CompareTo(ByVal obj As Object) As Integer Implements IComparable.CompareTo
        If obj.GetType() Is GetType(SpeechInstance) Then
            Dim comp As SpeechInstance = DirectCast(obj, SpeechInstance)
            Return can.CompareTo(comp.can)
        Else
            Throw New System.ArgumentException("Unable to compare these types!")
        End If
    End Function
    Function EqualCompare(ByVal other As SpeechInstance) As Boolean Implements IEquatable(Of SpeechInstance).Equals
        If GUID = other.GUID Then
            Return True
        Else
            Return False
        End If
    End Function
    Private Function getTextWidth(ByRef text As String) As Integer
        Dim zFontFamily As String = "Microsoft Sans Serif"
        Dim zFontSize As Single = 8
        Dim zFontStyle As FontStyle = FontStyle.Regular
        Dim zFontSizeUnit As GraphicsUnit = GraphicsUnit.Pixel
        Dim zTextSize1 As Size = TextRenderer.MeasureText(text, New Font(zFontFamily, zFontSize, zFontStyle, zFontSizeUnit))
        Return zTextSize1.Width
    End Function
    Private Function getTextHeight(ByRef lines As Integer) As Integer
        Dim zFontFamily As String = "Microsoft Sans Serif"
        Dim zFontSize As Single = 8.25
        Dim zFontStyle As FontStyle = FontStyle.Regular
        Dim zFontSizeUnit As GraphicsUnit = GraphicsUnit.Pixel
        Dim zTextSize1 As Size = TextRenderer.MeasureText("This is a sample string.", New Font(zFontFamily, zFontSize, zFontStyle, zFontSizeUnit))
        Return (zTextSize1.Height + 2) * lines + 20
    End Function
    <Xml.Serialization.XmlIgnore()> Public ReadOnly Property Controls() As List(Of Control)
        Get
            For Each item As Control In ControlsInternal
                If item.GetType() Is GetType(TextBox) Then
                    Dim tb As TextBox = DirectCast(item, TextBox)
                    AddHandler tb.TextChanged, AddressOf modify
                End If
                If item.GetType() Is GetType(ComboBox) Then
                    Dim cb As ComboBox = DirectCast(item, ComboBox)
                    AddHandler cb.SelectedIndexChanged, AddressOf modify
                End If
                If item.GetType() Is GetType(CheckBox) Then
                    Dim cb As CheckBox = DirectCast(item, CheckBox)
                    AddHandler cb.CheckedChanged, AddressOf modify
                End If
            Next
            Return ControlsInternal
        End Get
    End Property
    Public Sub TerminateEditor()
        RaiseEvent TerminateEditors()
    End Sub
    Private Sub modify(ByVal obj As Object, ByVal e As System.EventArgs)
        RaiseEvent Modified()
    End Sub
    Public Function Print() As String
        Dim workingtext As String
        workingtext = can.Text
        Dim outputText As String = workingtext
        For Each checkMatch As Match In checkRegex.Matches(workingtext)
            For Each item As Control In Controls
                If item.GetType Is GetType(CheckBox) And item.Name = checkMatch.Groups("name").Value Then
                    Dim cb As CheckBox = DirectCast(item, CheckBox)
                    If cb.Checked Then
                        outputText = outputText.Replace(checkMatch.Value, checkMatch.Groups("text").Value)
                    Else
                        outputText = outputText.Replace(checkMatch.Value, "")
                    End If
                End If
            Next
        Next
        workingtext = outputText
        For Each fieldMatch As Match In fieldRegex.Matches(workingtext)
            For Each item As Control In Controls
                If item.GetType Is GetType(TextBox) And item.Name = fieldMatch.Groups("name").Value Then
                    Dim tb As TextBox = DirectCast(item, TextBox)
                    outputText = outputText.Replace(fieldMatch.Value, tb.Text)
                End If
            Next
        Next
        workingtext = outputText
        For Each comboMatch As Match In comboRegex.Matches(workingtext)
            For Each item As Control In Controls
                If item.GetType() Is GetType(ComboBox) And item.Name = comboMatch.Groups("name").Value Then
                    Dim cb As ComboBox = DirectCast(item, ComboBox)
                    For Each optionMatch As Match In optionRegex.Matches(comboMatch.Groups("options").Value)
                        If CStr(cb.SelectedItem) = optionMatch.Groups("name").Value Then
                            outputText = outputText.Replace(comboMatch.Value, optionMatch.Groups("text").Value)
                        End If
                    Next
                End If
            Next
        Next
        If outputText.Trim <> "" Then
            Return outputText + Environment.NewLine + Environment.NewLine
        Else
            Return ""
        End If
    End Function
    Public ReadOnly Property SafeMode() As Boolean
        Get
            Return can.SafeMode
        End Get
    End Property
    Public ReadOnly Property logs() As List(Of String)
        Get
            Dim retVar As New List(Of String)
            For Each item As String In can.LogsGenerated.Split(CChar(Environment.NewLine))
                If item.Trim <> "" Then
                    retVar.Add(item)
                End If
            Next
            Return retVar
        End Get
    End Property
    Private internalSerializedControls As List(Of SerializedControlProperty)
    <OnSerializing()> Private Sub OnSerializing(ByVal context As StreamingContext)
        '
        'What we need to do is serialize the controls in ControlsInternal. But we can't do that, because
        'control is not a serializable class. This function builds the InternalSerializedControls list
        'by copying out the important bits from each of the controls in the ControlsInternal variable
        '
        internalSerializedControls = New List(Of SerializedControlProperty)
        For Each item As Control In ControlsInternal
            If item.GetType() Is GetType(TextBox) Then
                Dim saver As New SerializedControlProperty
                saver.Name = item.Name
                saver.Text = item.Text
                saver.Type = "TextBox"
                internalSerializedControls.Add(saver)
            ElseIf item.GetType() Is GetType(ComboBox) Then
                Dim cb As ComboBox = CType(item, ComboBox)
                Dim saver As New SerializedControlProperty
                saver.Name = item.Name
                saver.Text = CStr(cb.SelectedItem)
                saver.Type = "ComboBox"
                internalSerializedControls.Add(saver)
            ElseIf item.GetType() Is GetType(CheckBox) Then
                Dim cb As CheckBox = CType(item, CheckBox)
                Dim saver As New SerializedControlProperty
                saver.Name = item.Name
                If cb.Checked Then
                    saver.Text = "Checked"
                Else
                    saver.Text = "Cleared"
                End If
                saver.Type = "CheckBox"
                internalSerializedControls.Add(saver)
            End If
        Next
    End Sub
    <OnSerialized()> Private Sub OnSerialized(ByVal context As StreamingContext)
        'Destroy the serialization list because we no longer need it.
        internalSerializedControls = Nothing
    End Sub
    <OnDeserializing()> Private Sub OnDeSerializing(ByVal context As StreamingContext)
        'Instantiate the list so that the deserializer can fill it.
        internalSerializedControls = New List(Of SerializedControlProperty)
    End Sub
    <OnDeserialized()> Private Sub OnDeSerialized(ByVal context As StreamingContext)
        FillControls()
        For Each item As SerializedControlProperty In internalSerializedControls
            If item.Type = "ComboBox" Then
                For Each ControlItem As Control In ControlsInternal
                    If ControlItem.GetType() Is GetType(ComboBox) And ControlItem.Name = item.Name Then
                        Dim cb As ComboBox = CType(ControlItem, ComboBox)
                        cb.SelectedItem = item.Text
                    End If
                Next
            End If
            If item.Type = "TextBox" Then
                For Each ControlItem As Control In ControlsInternal
                    If ControlItem.GetType() Is GetType(TextBox) And ControlItem.Name = item.Name Then
                        Dim cb As TextBox = CType(ControlItem, TextBox)
                        cb.Text = item.Text
                    End If
                Next
            End If
            If item.Type = "CheckBox" Then
                For Each ControlItem As Control In ControlsInternal
                    If ControlItem.GetType() Is GetType(CheckBox) And ControlItem.Name = item.Name Then
                        Dim cb As CheckBox = CType(ControlItem, CheckBox)
                        If item.Text = "Checked" Then
                            cb.Checked = True
                        Else
                            cb.Checked = False
                        End If
                    End If
                Next
            End If
        Next
        'Destroy the serialization list because we no longer need it.
        internalSerializedControls = Nothing
        dNonSerializableHandlers = New List(Of Generic)
        dSerializableHandlers = New List(Of Generic)
        mNonSerializableHandlers = New List(Of Generic)
        mSerializableHandlers = New List(Of Generic)
    End Sub
End Class
